#include "FTPlib.h"
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <netdb.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include "strmsg.h"

struct sockaddr_in serverAddr, localAddress, serverAddrData, clientAddrData;
int pControl, pData;
int pActive = 0;
struct hostent * servidor;

//indicando que no se realizó ningún tipo de conexión.
int sockControl = -2;
int sockData = -2;

int dataOnline = 0;
int esPasivo = 0;

FILE *output; //Output stream para los mensajes del servidor

					/* MÉTODOS AUXILIARES */
					

/*
 * Establece la salida del módulo según el parámetro ingresado en 'out'
 */
void setOutput(FILE *out){
	output = out;
}

/*
 * Se encarga de abrir la conexión para la transferencia de datos,
 * ya sea en modo activo o pasivo.
 */
int openDatos(){

	sockData = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
	if(sockData <= -1) fprintf(output,"error al crear el socket\n");
	
	if(!esPasivo){
		memset(&clientAddrData,0,sizeof(clientAddrData));
		clientAddrData.sin_family = AF_INET;		
		clientAddrData.sin_port   = htons(pActive);	
		clientAddrData.sin_addr   = localAddress.sin_addr;
		
		const int       optVal = 1;
		const socklen_t optLen = sizeof(optVal);

		setsockopt(sockData, SOL_SOCKET, SO_REUSEADDR, (void*) &optVal, optLen);
		
		if(bind(sockData,(struct sockaddr *) &clientAddrData,sizeof(clientAddrData))){
			fprintf(output,"no bindea\n");
			return -1;
		}
			
		if(listen(sockData,1)){
			fprintf(output,"No escucha\n");		
			return -1;
		}		
	}
	else{
	
		memset(&serverAddrData,0,sizeof(serverAddrData));
		serverAddrData.sin_family = serverAddr.sin_family;
		serverAddrData.sin_port   = htons(pData);	
		serverAddrData.sin_addr	  = serverAddr.sin_addr;						
		
		if(connect(sockData,(struct sockaddr*)&serverAddrData,sizeof(serverAddrData)) <= -1){
			fprintf(output,"error al conectar\n");
			return -1;
		 }
	}
	return 0;
}

/*
 * Calcula el puerto en el cual el servidor está escuchando para la 
 * transferencia de datos.
 */
int puertoRemoto(char *msgPuerto){

strtok(msgPuerto,"(");

int i;
int port = 0;
int ip[4];

	for( i = 0; i < 6; i++ )
	{
			char* val = strtok(NULL,",");

			if(!val)
				break;

			if( i >= 0 && i < 4 )
			{
				ip[i] = strtol(val,NULL,10);
			}
			else if( 4 == i )
			{
				port = strtol(val,NULL,10)*256;
			}
			else if( 5 == i )
			{
				port += strtol(val,NULL,10);
			}
	}

return port;
}

/*
 * Se encarga de interpretar la respuesta enviada por el servidor (response)
 * considerando los códigos presentes en el RFC del protocolo FTP, imprimiendo
 * los mensajes correspondientes y realizando cualquier acción considerada
 * adecuada.
 * Devuelve el código enviado por el servidor.
 */
int interpretar(char response[]){
	//Se extrae el codigo de la respuesta, para luego realizar
	//un chequeo para cada caso.
	int code = atoi(strtok(response," "));
	switch (code){
		case 220: //servicio listo para nuevo usuario
			fprintf(output,"%s\n",MSG_220);
			fprintf(output,"/=>usuario:");
			char usr[256];
			fgets(usr,256,stdin);
			user(usr);			
			break;
		case 226: //Acción de datos exitosa, cerrando conexión de datos
			fprintf(output,"%s\n",MSG_226);
			break;
		case 257: //Directorio actual
			fprintf(output,"%s%s\n",MSG_257,strtok(NULL," "));
			break;
		case 200: //comando aceptado, mensaje genérico
			fprintf(output,"%s (%s)\n",MSG_200,strtok(NULL,"\r"));
			break;
		case 250: //directorio cambiado exitosamente
			fprintf(output,"%s\n",MSG_250);
			break;
		case 221: //cerrando conexión de control
			fprintf(output,"%s\n",MSG_221);
			break;
		case 230: //usuario logueado.
			fprintf(output,"%s\n",MSG_230);
			break;
		case 150: //Abriendo conexión de datos
			fprintf(output,"%s\n",MSG_150);
			break;
		case 125: //Conexión de datos ya abierta. Comenzando transferencia
			fprintf(output,"%s\n",MSG_125);
			break;
		case 331: //nombre de usuario requiere de contraseña
			fprintf(output,"%s\n",MSG_331);
			fprintf(output,"/=>password:");
			char *password;
			fgets(password,256,stdin);
			pass(password);			
			break;
		case 227: //Utilizando modo pasivo
			fprintf(output,"%s\n",MSG_227);
			break;
		case 425: //Error! No se puede abrir la conexión de datos
			fprintf(output,"%s\n",MSG_425);
			break;
		case 421: //Servicio no disponible. Cerrando la conexión de datos
			fprintf(output,"%s\n",MSG_421);
			break;		
		case 530: // No se pudo autenticar 
			fprintf(output,"%s\n",MSG_530);
			fprintf(output,"/=>usuario:");
			char usuario[256];
			fgets(usuario,256,stdin);
			
			user(usuario);			
			break;
		case 550: //Archivo o directorio no disponible
			fprintf(output,"%s\n",MSG_550);
			break;
		default: //default, imprime directamente el mensaje del servidor.
			fprintf(output,"%s %s\n",response,strtok(NULL,"\r"));
			break;
	}
	return code;
	
}

					/* FUNCIONES DE LA LIBRERÍA*/
 
/*
 * Abre una conexión de control hacia el host indicado por la ip (o dominio)
 * y el puerto (default: 21)
 * Devuelve -1 si la conexión falla. De lo contrario, devuelve 0.
 */
int open(char *ip,char *puerto){
	
	int resul = -1;
	char welcome[1024];
	
	servidor = gethostbyname(ip);
	if (servidor == NULL){
		fprintf(output,"Error, no se reconoce el sitio\n");
		return 	resul;	
	}
	sockControl = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);			
	
	memset(&serverAddr,0,sizeof(serverAddr));
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_port   = htons(atoi(puerto));	
	serverAddr.sin_addr   = *((struct in_addr*)servidor->h_addr);	
	

	esPasivo = 0;
	pControl = atoi(puerto);
	pData = pControl-1;

	resul = connect(sockControl,(struct sockaddr*)&serverAddr,sizeof(serverAddr));	
	int n = recv(sockControl,welcome,1024,0);	
	if(n>=0){ //si n<0 la conexión falló
		welcome[n] = '\0';
		interpretar(welcome);
	}
	return resul;
}

/*
 * Envía el comando USER al servidor, con el nombre de usuario dado (username)
 */
void user(char username[]){
	char command[256];
	char msg[256];
	
	//Calculamos la longitud del nombre de usuario ingresado.
	int nameLength;
	for(nameLength=0;username[nameLength] != '\0';nameLength++);	
	
	sprintf(command,"USER %s",username);
	
	send(sockControl,command,nameLength+5,0);
	int n=recv(sockControl,msg,256,0);
	
	msg[n] = '\0';
	interpretar(msg);	
	memset(msg,0,strlen(msg));
	
}

/*
 * Envía el comando PASS al servidor, con la contraseña especificada
 * (pass)
 */
void pass(char pass[]){
	char command[256];	
	char msg[256];
		
	//Calculamos la longitud de la pass ingresada.
	int passLength;
	for(passLength=0;pass[passLength] != '\0';passLength++);	
	
	sprintf(command,"PASS %s",pass);
	
	send(sockControl,command,passLength+5,0);
	int n=recv(sockControl,msg,256,0);
	
	msg[n] = '\0';
	interpretar(msg);
}

/*
 * Envía el comando PWD, esperando que el servidor indique el directorio
 * actual.
 */
void pwd(){
	char msg[256];
	int n;
	
	send(sockControl,"PWD\n",4,0);
	
	n = recv(sockControl,msg,256,0);
	msg[n] = '\0';
	interpretar(msg);	
	memset(msg,0,strlen(msg)); //se borra el contenido del string.
}

/*
 * Envía el comando LIST, esperando que el servidor envíe confirmación
 * y la lista de directorios y archivos mediante la conexión de datos.
 * Esta operación se encarga de solicitar las conexion de datos en el caso de 
 * no existir alguna, respetando el modo seleccionado por el usuario.
 */
void list(){
	char msg[256];	
	char data[1000];	
	int nCon = 0;
	int nDato = 0;	


	if(!dataOnline){		
		if (esPasivo)
			passive(NULL);
		else
			active(NULL);
	}
	
	openDatos();//abre la conexión de datos.
	
	send(sockControl,"LIST\n",5,0);
	nCon = recv(sockControl,msg,256,0);			
	
	msg[nCon] = '\0';
	interpretar(msg);
	memset(msg,0,strlen(msg));
	
	memset(data,0,1000);
	
	int tmpSock = sockData;
	//se trabaja de acuerdo al modo de datos especificado por el usuario (o el default).
	if(!esPasivo)	
		tmpSock = accept(sockData,NULL,NULL);
	while(1){			
			nDato = recv(tmpSock,data,1000,MSG_WAITALL);					
			
			data[nDato] = '\0';
			fprintf(output,"%s",data);
			memset(data,0,1000);
			
		if (nDato <= 0){				
			break;
		}
	}	
		
	nCon = recv(sockControl,msg,256,0);
	msg[nCon] = '\0';
	interpretar(msg);
	memset(msg,0,strlen(msg));
	
	//Cerramos la conexión, y lo indicamos modificando la variable adecuada.
	shutdown(sockData,2);
	close(sockData);
	dataOnline = 0;


}
/*
 * Envía el comando PASV al servidor, indicando que el modo de conexión
 * para datos es pasivo.
 */
void passive(){

	char msg[256];
	int n = 0;	
	
	send(sockControl,"PASV\n",5,0);
	
	n = recv(sockControl,msg,256,0);		
	pData = puertoRemoto(msg);
	msg[n] = '\0';
	interpretar(msg);	
	esPasivo = 1;
	
	memset(msg,0,strlen(msg));
	dataOnline = 1;
}

/*
 * Envía el comando PORT al servidor, indicando que el modo de conexión
 * para datos es pasivo.
 * Para la selección del puerto en donde se escucha, se toma primero el
 * puerto siguiente al de control, luego, recursivamente, el siguiente al
 * puerto anterior de datos.
 */
void active(){	
	char msg[256];
	char command[256];
	char host[100];
	int n = 0;	
	
	char todo[30];
	char final[30];
	
	esPasivo = 0;
	
	char *direc = inet_ntoa(serverAddr.sin_addr); // return the IP	
	
	if(pActive == 0){//si el puerto para el modo activo nunca se seteó...
	
		socklen_t addressLength = sizeof(localAddress);

		getsockname(sockControl, (struct sockaddr*)&localAddress,  &addressLength);

		pActive = (int) ntohs(localAddress.sin_port)+1;
	}
	else //si el puerto activo ya se había establecido, usamos el siguiente.
		pActive++;
	
	int div = (pActive) / 256;
	int mod = (pActive) % 256;
	
	sprintf(todo,"%s,%d,%d\n",inet_ntoa( localAddress.sin_addr),div,mod);
	
	int count;

	int actual = 0;

	for ( count = 0; count < sizeof(todo); count++)
		{
			if (todo[count] == '.')
			{
				final[actual++] = ',';				

			} else {
				final[actual++] = todo[count];
			}
		}

		
	int todolength;
	for(todolength=0;final[todolength] != '\0';todolength++);	
	sprintf(command,"PORT %s",final);	
		
	int rta = send(sockControl,command,todolength+5,0);

		n = recv(sockControl,msg,256,0);	
	
	msg[n] = '\0';	
	interpretar(msg);
	memset(msg,0,strlen(msg));
		
	dataOnline = 1;
}

/*
 * Cierra la conexión, de existir alguna.
 */
void quit(){
	if(sockControl > -1){
		char msg[128];
		send(sockControl,"QUIT\n",5,0);
		int n = recv(sockControl,msg,128,0);
		
		msg[n] = '\0';
		interpretar(msg);
		memset(msg,0,strlen(msg));
		
		shutdown(sockControl, 2);
		close(sockControl);
	}
		exit(0);
}

/*
 * Envia el comando CWD al servidor, cambiando el directorio de trabajo.
 */
void cd(char dir[]){

	char msg[256];
	char command[256];
	
	int dirLength;
	for(dirLength=0;dir[dirLength] != '\0';dirLength++);

	sprintf(command,"CWD %s",dir);
	
	send(sockControl,command,dirLength+4,0);	
	int n=recv(sockControl,msg,256,0);
	
	interpretar(msg);		
}

/*
 * Intenta, enviando el comando RETR, descargar un archivo alojado en el
 * servidor, utilizando el modo de conexión de datos adecuado.
 * 'get' sobreescribe cualquier archivo existente con el mismo nombre
 * que el archivo a descargar.
 */
void get(char file[]){

	char command[256];	
	char msg[256];
	char data[16384];	

	int nDato = 0;
	int nCon = 0;
	
	//Calculamos la longitud del nombre de archivo ingresado.
	int fileLength;
	for(fileLength=0;file[fileLength] != '\0';fileLength++);	
	
	if(!dataOnline){		
		if (esPasivo)
			passive(NULL);
		else
			active(NULL);
	}	
	
	if(openDatos())
		return;
	
	sprintf(command,"RETR %s",file);
	send(sockControl,command,fileLength+5,0);
	
	nCon = recv(sockControl,msg,256,0);	
	msg[nCon] = '\0';	
	if(interpretar(msg) == 550){
			return;
	}	
	
	file[strlen(file)-1] = '\0';
	
	fopen(file,"w");
	FILE *dest = fopen(file,"a");
	
	if(esPasivo){
		while(1){			
			nDato = recv(sockData,data,16384,MSG_WAITALL);
			if (nDato == 0){ //si el peso del archivo era múltiplo 16384
				printf("ndato == %i\n",nDato);
				break;
			}
			if (nDato < 16384){			
				fwrite(data,nDato,1,dest);				
				break;
			
			}			
			fwrite(data,nDato,1,dest);
		}
	}
	else{
		int tempSock = accept(sockData,NULL,NULL);
		while(1){			
			nDato = recv(tempSock,data,16384,MSG_WAITALL);			
			if (nDato == 0){ //si el peso del archivo era múltiplo 16384
				break;
			}
			if (nDato < 16384){			
				fwrite(data,nDato,1,dest);
				break;					
			}			
			fwrite(data,nDato,1,dest);
		}
	}
	fclose(dest);
	shutdown(sockData,2);
	close(sockData);
	
	dataOnline = 0;
	nCon = recv(sockControl,msg,256,0);	
	msg[nCon] = '\0';
	interpretar(msg);
}

/*
 * Transfiere un archivo local al servidor FTP, utilizando el modo
 * adecuado.
 */
void put(char file[]){

		char buf[16384];
 
		char command[256];     
		char msg[256];
		char data[16384];
		char todo[1256];
 
		int nDato = 0;
		int nCon = 0;
 
		int nread = 0;
	   
		//Calculamos la longitud del nombre de archivo ingresado.
		int fileLength;
		for(fileLength=0;file[fileLength] != '\0';fileLength++); 
		
		if(!dataOnline){		
		if (esPasivo)
			passive(NULL);
		else
			active(NULL);
	}				
		
		sprintf(command,"STOR %s",file);
				
		file[strlen(file)-1] = '\0';		
		FILE *dest = fopen(file,"r");					
		if (dest == NULL){
			fprintf(output,"Arhivo inexistente!\n");		
			return;
		}
		
		openDatos();
		
		send(sockControl,command,fileLength+5,0);
		nCon = recv(sockControl,msg,256,0);			
		
		msg[nCon] = '\0';
		interpretar(msg);
		memset(msg,0,strlen(msg));
		
		int total = 0;
		int resto = 0;
		if(esPasivo){
			while(1){
				nread = fread(buf,1,sizeof(buf),dest);					
				if (nread <= 0 && feof(dest))				
					break;			
				send(sockData,buf,nread,0);			
			}
		}
		else{
			int acceptedDataCon = accept(sockData,NULL,NULL);
			while(1){
				nread = fread(buf,1,sizeof(buf),dest);					
				if (nread <= 0 && feof(dest)){
					shutdown(acceptedDataCon,2);
					close(acceptedDataCon);				
					break;
				}
				send(acceptedDataCon,buf,nread,0);			
			}
		}
		fclose(dest);
				
		shutdown(sockData,2);
		close(sockData);
		dataOnline = 0;  
	
	
		nCon =recv(sockControl,msg,256,0);
		
		msg[nCon] = '\0';
		interpretar(msg);
		memset(msg,0,strlen(msg));			
}



